<!doctype html>

<html>

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0">

  <script src="../../webcomponentsjs/webcomponents-lite.js"></script>
  <script src="../../web-component-tester/browser.js"></script>
  <script src="../../iron-test-helpers/mock-interactions.js"></script>
  <link rel="import" href="../../test-fixture/test-fixture.html">

  <link rel="import" href="helpers.html">
  <link rel="import" href="../vaadin-grid.html">
</head>

<body>

  <test-fixture id="default">
    <template>
      <vaadin-grid style="width: 60px; height: 400px;" size="10">
        <vaadin-grid-column width="200px">
          <template class="header">header</template>
          <template>[[index]]</template>
          <template class="footer">footer</template>
        </vaadin-grid-column>
        <vaadin-grid-column width="200px">
          <template class="header">header</template>
          <template>[[index]]</template>
          <template class="footer">footer</template>
        </vaadin-grid-column>
      </vaadin-grid>
    </template>
  </test-fixture>

  <test-fixture id="content-only">
    <template>
      <vaadin-grid style="width: 50px; height: 400px;" size="1">
        <vaadin-grid-column width="200px" frozen>
          <template>
            <div style="height: 1000px;"></div>
          </template>
        </vaadin-grid-column>
      </vaadin-grid>
    </template>
  </test-fixture>

  <test-fixture id="scrollable-content">
    <template>
      <vaadin-grid style="width: 200px; height: 400px;" size="100">
        <vaadin-grid-column>
          <template>
            <div style="height: 100px; overflow: auto;">
              <div style="height: 1000px;"></div>
            </div>
          </template>
        </vaadin-grid-column>
      </vaadin-grid>
    </template>
  </test-fixture>

  <script>
    const ios = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;

    describe('outer scroller', () => {
      let grid;
      let scroller;
      let outerScroller;
      let target;
      let tableRect;

      beforeEach(done => {

        grid = fixture('default');

        flushColumns(grid);
        grid.dataProvider = infiniteDataProvider;
        flushGrid(grid);
        scroller = grid.$.scroller;
        outerScroller = grid.$.outerscroller;
        outerScroller.hidden = false;
        target = outerScroller.scrollTarget;
        tableRect = grid.$.table.getBoundingClientRect();

        animationFrameFlush(() => {
          outerScroller._syncingScrollTarget = false;
          outerScroller._syncingOuterScroller = false;
          done();
        });
      });

      it('should cancel wheel event', () => {
        grid.style.fontSize = '12px';
        const e = wheel(getBodyCellContent(grid, 0, 0), 1, 1);
        expect(e.defaultPrevented).to.be.true;
      });

      it('should not cancel wheel event', () => {
        const e = wheel(getBodyCellContent(grid, 0, 0), 1, 1, true);
        expect(e.defaultPrevented).to.be.false;
      });

      it('should invoke onScroll synchronously', done => {
        const spy = sinon.stub(grid, '_translateStationaryElements', done);
        outerScroller._syncScrollTarget();
      });

      it('should invoke frozen element translate handler', done => {
        const spy = sinon.stub(grid, '_translateStationaryElements', () => {
          spy.restore();
          done();
        });
        outerScroller.scrollLeft = 1;
      });

      it('should have the same size as the scroller', () => {
        const outerRect = outerScroller.getBoundingClientRect();
        expect(outerRect.left).to.eql(tableRect.left);
        expect(outerRect.right).to.eql(tableRect.right);
        expect(outerRect.top).to.eql(tableRect.top);
        expect(outerRect.bottom).to.eql(tableRect.bottom);
      });

      it('should have the same scrollHeight/scrollWidth as the target', () => {
        expect(outerScroller.scrollHeight).to.eql(target.scrollHeight);
        expect(outerScroller.scrollWidth).to.eql(target.scrollWidth);
      });

      it('should have the same scrollHeight/scrollWidth as the target (em column width)', () => {
        grid.querySelector('vaadin-grid-column').width = '10em';
        expect(outerScroller.scrollHeight).to.eql(target.scrollHeight);
        expect(outerScroller.scrollWidth).to.eql(target.scrollWidth);
      });

      it('should have correct scrollWidth with hidden columns', () => {
        grid.querySelector('vaadin-grid-column').hidden = true;
        // Hide the table's sizer and rely on the cells to size properly
        grid.$.fixedsizer.hidden = true;
        expect(outerScroller.scrollWidth).to.eql(target.scrollWidth);
      });

      describe('syncing with scroll target', () => {

        beforeEach(() => {
          outerScroller._syncingScrollTarget = false;
          outerScroller._syncingOuterScroller = false;
        });

        it('should scroll the target', done => {
          listenOnce(outerScroller, 'scroll', () => {
            animationFrameFlush(() => {
              expect(target.scrollLeft).to.equal(100);
              done();
            });
          });
          outerScroller.scrollLeft = 100;
        });

        // Outer scroller never needs to be synced on iOS since it's always the one that gets scrolled.
        if (!ios) {
          it('should scroll the outer scroller', () => {
            wheel(grid, 100, 0);
            expect(outerScroller.scrollLeft).to.equal(100);
          });

          it('should only sync outer scroller when wheel scrolling host or passthrough', () => {
            const spy = sinon.spy(outerScroller, 'syncOuterScroller');

            // Not wheelscrolling nor passthrough
            outerScroller.passthrough = false;
            grid._scrollHandler();
            expect(spy.called).to.be.false;

            // Wheel scrolling only
            grid._wheelScrolling = true;
            grid._scrollHandler();
            expect(spy.called).to.be.true;

            // Passthrough only
            spy.reset();
            grid._wheelScrolling = false;
            outerScroller.passthrough = true;
            grid._scrollHandler();
            expect(spy.called).to.be.true;
          });
        }

        it('should not invoke afterScroll on scroll event while outer scrolling', () => {
          const spy = sinon.spy(grid, '_afterScroll');
          outerScroller.dispatchEvent(new Event('mousedown'));
          grid.$.table.dispatchEvent(new Event('scroll'));
          expect(spy.called).to.be.false;
        });

      });

      if (!ios) {
        describe('passtrough pointer events', () => {

          function move(x, y) {
            makeMouseEvent('mousemove', {x: x, y: y}, scroller);
          }

          const HAS_NEW_MOUSE = (() => {
            let has = false;
            try {
              has = Boolean(new MouseEvent('x'));
            } catch (_) {
              // continue regardless of error
            }
            return has;
          })();

          function makeMouseEvent(type, xy, node) {
            const props = {
              bubbles: true,
              cancelable: true,
              clientX: xy.x,
              clientY: xy.y,
              // Make this a primary input.
              buttons: 1 // http://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/buttons
            };
            let e;
            if (HAS_NEW_MOUSE) {
              e = new MouseEvent(type, props);
            } else {
              e = document.createEvent('MouseEvent');
              e.initMouseEvent(
                type, props.bubbles, props.cancelable,
                null, /* view */
                null, /* detail */
                0, /* screenX */
                0, /* screenY */
                props.clientX, props.clientY,
                false, /* ctrlKey */
                false, /* altKey */
                false, /* shiftKey */
                false, /* metaKey */
                0, /* button */
                null /* relatedTarget*/);
            }
            node.dispatchEvent(e);
          }

          function outerScrollerAccessible() {
            return window.getComputedStyle(outerScroller).pointerEvents !== 'none';
          }

          function outerScrollerScrollable() {
            return window.getComputedStyle(outerScroller).overflow !== 'hidden';
          }

          beforeEach(() => {
            flushGrid(grid);
          });

          it('should passtrough pointer events', done => {
            listenOnce(scroller, 'mousemove', () => {
              expect(outerScrollerAccessible()).to.be.false;
              done();
            });

            move(1, 1);
          });

          it('should not passtrough if and only if scrollbars exist (vertical scrollbar hover)', done => {
            listenOnce(scroller, 'mousemove', () => {
              expect(outerScrollerAccessible()).to.equal(getScrollbarWidth() > 0);
              done();
            });
            move(tableRect.width, 1);
          });

          it('should not passtrough if and only if scrollbars exist (horizontal scrollbar hover)', done => {
            listenOnce(scroller, 'mousemove', () => {
              expect(outerScrollerAccessible()).to.equal(getScrollbarWidth() > 0);
              done();
            });
            move(1, tableRect.height);
          });

          it('should not passtrough if hovered after srcoll period', done => {
            grid._scrollHandler();
            listenOnce(scroller, 'mousemove', () => {
              expect(outerScrollerAccessible()).to.equal(outerScrollerScrollable());
              done();
            });
            requestAnimationFrame(() => {
              move(tableRect.width, 1);
            });
          });

        });
      }

    });

    describe('content-only', () => {
      let grid;
      let scroller;
      let outerScroller;

      beforeEach(() => {
        grid = fixture('content-only');
        grid.dataProvider = infiniteDataProvider;
        scroller = grid.$.scroller;
        outerScroller = grid.$.outerscroller;
        outerScroller.hidden = false;
        flushGrid(grid);
      });

      it('should have the correct scrollHeight', () => {
        const rows = getRows(grid.$.items);
        const cells = getRowCells(rows[0]);
        const expectedScrollHeight = cells[0].offsetHeight;
        expect(outerScroller.scrollHeight).to.eql(expectedScrollHeight);
      });
    });

    describe('scrollable-content', () => {
      let grid, scrollable;

      beforeEach(done => {

        grid = fixture('scrollable-content');

        flushGrid(grid);

        grid.dataProvider = infiniteDataProvider;
        Polymer.flush();
        if (grid._observer.flush) {
          grid._observer.flush();
        }
        scrollable = getBodyCellContent(grid, 0, 0).firstElementChild;
        animationFrameFlush(() => done());
      });

      it('should not cancel wheel events for scrollable content', () => {
        const e = wheel(scrollable, 0, 1);
        expect(e.defaultPrevented).to.be.false;
      });

      it('should not scroll the grid', () => {
        const scrollTop = grid.$.scroller._scrollTop;
        const e = wheel(scrollable, 0, 1);
        expect(grid.$.scroller._scrollTop).to.equal(scrollTop);
      });

      it('should cancel wheel events for scrollable content (scrolled to end)', () => {
        scrollable.scrollTop = scrollable.scrollHeight;
        const e = wheel(scrollable, 0, 1);
        expect(e.defaultPrevented).to.be.true;
      });

      it('should cancel wheel events for non-scrollable content', () => {
        scrollable.style.overflow = 'visible';
        let e = wheel(scrollable, 0, 1);
        expect(e.defaultPrevented).to.be.true;
        scrollable.style.overflow = 'hidden';
        e = wheel(scrollable, 0, 1);
        expect(e.defaultPrevented).to.be.true;
      });

      it('should scroll the grid (scrolled to end)', () => {
        const scrollTop = grid._scrollTop;
        scrollable.scrollTop = scrollable.scrollHeight;
        const e = wheel(scrollable, 0, 100);
        expect(grid._scrollTop).to.equal(scrollTop + 100);
      });

    });
  </script>

</body>

</html>
